/*
 * commons is a based project implemented
 * Copyright (C) 2024 Jasmine
 */
package com.commons.redis.aop;

import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import com.commons.core.exception.GlobalErrorInfoException;
import com.commons.redis.anno.RepeatSubmitLimit;
import com.commons.redis.utils.RedisUtils;

/**
 * @author Jasmine
 * @version 1.0
 * @date 2021-12-13 15:35:35
 * @see RepeatSubmitLimitAspect
 * @since JDK1.8
 * 
 *        <pre>
 *        &#64;Before("@annotation(noRepeatSubmit)")
 *        public void handleRepeat(JoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {
 *        </pre>
 */
@Aspect
@Component
public class RepeatSubmitLimitAspect {
    private static Logger log = LoggerFactory.getLogger(RepeatSubmitLimitAspect.class);

    private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    /**
     * Spel表达式解析器
     */
    private static final ExpressionParser PARSER = new SpelExpressionParser();

    @Around("@annotation(repeatSubmitLimit)")
    public Object handleSubmit(ProceedingJoinPoint joinPoint, RepeatSubmitLimit repeatSubmitLimit) throws Throwable {
        // 生成完整的 Redis key
        String lockKey = getLockKey(joinPoint, repeatSubmitLimit);

        // 公平加锁，lockTime后锁自动释放
        boolean isLocked = false;

        try {
            isLocked = RedisUtils.fairLock(lockKey, repeatSubmitLimit.expire(), repeatSubmitLimit.timeUnit());
            if (isLocked) {
                return joinPoint.proceed();
            } else {
                throw new GlobalErrorInfoException("请勿重复提交");
            }

        } catch (Throwable e) {
            log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'",
                joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
                e.getCause() != null ? e.getCause() : "NULL", e.getMessage(), e);
            throw e;
        } finally {
            // 如果锁还存在，释放锁。
            if (isLocked) {
                RedisUtils.CLIENT.getLock(lockKey);
            }
        }
    }

    private String getLockKey(ProceedingJoinPoint joinPoint, RepeatSubmitLimit repeatSubmitLimit) {
        String businessKey = "no-repeat:" + repeatSubmitLimit.businessKey();
        boolean userLimit = repeatSubmitLimit.userLimit();
        String businessParam = repeatSubmitLimit.businessParam();
        /*TODO if (userLimit) {
            businessKey = businessKey + ":" + LoginUserHolder.getCurrentUser().getId();
        }
        */
        if (StringUtils.isNotBlank(businessParam)) {
            Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
            EvaluationContext context =
                new MethodBasedEvaluationContext(null, method, joinPoint.getArgs(), NAME_DISCOVERER);
            String key = PARSER.parseExpression(businessParam).getValue(context, String.class);
            businessKey = businessKey + ":" + key;
        }
        return businessKey;
    }
}